#include "my_disk_key_lock.h"
#include "event/my_event_loop.h"

static inline int
my_disk_key_lock_node_cmp(my_disk_key_lock_node_t *a, my_disk_key_lock_node_t *b) {
    my_cache_key_t *a_key = a->req->key;
    my_cache_key_t *b_key = b->req->key;

    if (a_key->digest32 != b_key->digest32) {
        return (a_key->digest32  < b_key->digest32) ? -1 : 1;
    } else if (a_key->key_len != b_key->key_len) {
        return (a_key->key_len < b_key->key_len) ? -1 : 1;
    }

    return memcmp(a_key->key, a_key->key, a_key->key_len);
}

static void
my_rbtree_insert_disk_key_lock_node(my_rbtree_node_t *temp, my_rbtree_node_t *node,
    my_rbtree_node_t *sentinel) {

    my_rbtree_node_t **p;
    my_disk_key_lock_node_t *n, *t;

    for (; ;) {
        n = (my_disk_key_lock_node_t *)node;
        t = (my_disk_key_lock_node_t *)temp;

        // do not process the same key
        if (my_disk_key_lock_node_cmp(n, t) < 0) {
            p = &temp->left;
        } else {
            p = &temp->right;
        }

        if (*p == sentinel) {
            break;
        }

        temp = *p;
    }

    *p = node;
    node->parent = temp;
    node->left = sentinel;
    node->right = sentinel;
    my_rbt_red(node);

}

static my_disk_key_lock_node_t *
my_disk_key_lock_lookup(my_rbtree_t *rbtree, my_disk_key_lock_node_t *lock_node) {
    int rc;
    my_disk_key_lock_node_t *n;
    my_rbtree_node_t  *node, *sentinel;

    node = rbtree->root;
    sentinel = rbtree->sentinel;

    while(node != sentinel) {
        n = (my_disk_key_lock_node_t *)node;
        rc = my_disk_key_lock_node_cmp(n, lock_node);

        if (rc < 0) {
            node = node->right;
            continue;
        }

        if (rc > 0) {
            node = node->left;
            continue;
        }

        return n;
    }

    return NULL;
}

void
my_disk_key_lock_tab_init(my_disk_key_lock_tab_t *lock_tab) {
    bzero(lock_tab, sizeof(my_disk_key_lock_tab_t));
    my_spin_lock_init(&lock_tab->spin_lock);
    my_rbtree_init(&lock_tab->rbtree, &lock_tab->sentinel,
        my_rbtree_insert_disk_key_lock_node);
}

static void
my_disk_key_lock_handler(my_event_t *event) {
    my_disk_key_lock_node_t *lock_node = (my_disk_key_lock_node_t *)event->data;
    my_disk_key_lock_tab_t *lock_tab = (my_disk_key_lock_tab_t *)lock_node->lock_tab;

    my_disk_key_lock_node_t *t;
    my_spin_lock_lock(&lock_tab->spin_lock);
    t = my_disk_key_lock_lookup(&lock_tab->rbtree, lock_node);
    if (!t) {
        my_rbtree_insert(&lock_tab->rbtree, &lock_node->tree_node);
    }
    my_spin_lock_unlock(&lock_tab->spin_lock);

    if (t) {
        // retry in 1 msecond
        return my_event_loop_add_timer(&lock_node->event, 1);
    }

    return lock_node->done_cb(lock_node);
}

void
my_disk_key_lock_lock(my_disk_key_lock_node_t *lock_node) {
    lock_node->event.data = lock_node;
    lock_node->event.handler = my_disk_key_lock_handler;
    return my_disk_key_lock_handler(&lock_node->event);
}

void
my_disk_key_lock_unlock(my_disk_key_lock_node_t *lock_node) {
    my_disk_key_lock_tab_t *lock_tab = (my_disk_key_lock_tab_t *)lock_node->lock_tab;
    my_spin_lock_lock(&lock_tab->spin_lock);
    my_rbtree_delete(&lock_tab->rbtree, &lock_node->tree_node);
    my_spin_lock_unlock(&lock_tab->spin_lock);
}